package com.cheng.algorithm.search;

import java.util.Arrays;

/**
 * @User Administrator
 * @Classname FibonacciSearch
 * @Project Data-structure-and-algorithm
 * @Description 斐波那契（黄金分割法）查找介绍：
 * 黄金分割点：是指把一条线段分割为两部分，使基中一部分与全长之比等于另一部分与这部分之比。
 * 取其前三位数字的近似值是0.618。由于按此比例设计的造型十分美丽，因此称为黄金分割，也称为中外比。这是一个神奇的数字，会带来意向不大的效果。
 * 斐波那契数列：{1,1,2,3,5,8,13,21,34,55}，发现斐波那契数列的两个相邻数的比例，无限接近黄金分割值0.618
 * 斐波那契（黄金分割法）查找原理：
 * 斐波那契查找原理与前两种相似，仅仅改变了中间结点'(mid）的位置，mid不再是中间或插值得到，而是位于黄金分割点附近，即 mid=low+F(k-1)-1，low为斐波那契数列左边下标
 * 对F(k-1)-1的理解:
 * 1)由斐波那契数列F[K]=F[K-1]+F[K-2]的性质，可以得到（F[K]-1) =(F[k-1]-1)+(F[k-2]-1)+1。
 * 该式说明:只要顺序表的长度为F[k]-1，则可以将该表分成长度为F[k-1-1和F[k-2]-1的两段，从而中间位置为mid=low+F(k-1)-1
 * 2)类似的，每一子段也可以用相同的方式分割
 * 3)但顺序表长度n不一定刚好等于F[K]-1，所以需要将原来的顺序表长度n增加至F(K-1)。这里的k值只要能使得F[l]-1恰好大于或等于n即可，
 * 由以下代码得到,顺序表长度增加后，新增的位置（从n+1到F[k]-1位置)，都赋为n位置的值即可。
 * @Author wpcheng
 * @Create 2021-08-17-13:03
 * @Version 1.0
 */
public class FibonacciSearch {

    public static int maxSize = 20;//初始化斐波那契数列的大小

    public static void main(String[] args) {

        int[] arr = {1, 4, 7, 14, 45, 437, 678, 1885};
        int i = fibSearch(arr, 45);
        System.out.println(i);

    }

    //利用非递归方法，初始化一个斐波那契数列fbi
    public static int[] createFib() {

        int[] fbi = new int[maxSize];
        fbi[0] = 1;
        fbi[1] = 1;
        for (int i = 2; i < maxSize; i++) {
            fbi[i] = fbi[i - 1] + fbi[i - 2];
        }
        return fbi;
    }

    /**
     * 斐波那契查找算法：mid=low+F(k-1)-1
     *
     * @param arr 数组
     * @param key 要查找的数
     * @return 找到返回下标，没找到返回-1
     */
    public static int fibSearch(int[] arr, int key) {
        int low = 0; //数组左边下标
        int high = arr.length - 1; //数组右边下标
        int k = 0; //斐波那契数列黄金分割点的下标
        int mid = 0;
        int f[] = createFib(); // 得到斐波那契数列

        //得到斐波那契数列黄金分割点的下标
        while (high > f[k] - 1) {
            k++;
        }

        //因为 f[k]值可能大于 arr 的长度，因此我们需要构造一个新的数组，扩展arr
        //复制数组arr，复制后的数组长度为f[k]，不足的地方用0填充，例如：{1, 4, 7, 14, 45, 437, 678, 1885,0,0,0,0,0}
        int[] temp = Arrays.copyOf(arr, f[k]);
        //实际需求：数组arr后面用0填充部分用原arr数组的最后一个数代替，例如：{1, 4, 7, 14, 45, 437, 678, 1885,1885,1885,1885,1885,1885}
        for (int i = high + 1; i < temp.length; i++) {
            temp[i] = arr[high];
        }
        while (low < high) {
            mid = low + f[k - 1] - 1;
            //如果key < temp[mid]，说明key在temp[mid]左边，应该继续向数组左边查找
            if (key < temp[mid]) {
                high = mid - 1;
                /*
                为什么是k--
                说明:
                1。全部元素=前面的元素＋后边元素
                2.f[k] = f[k-1] + f[k-2]
                因为前面有f[k-1]个元素,所以可以继续拆分f[k-1] = f[k-2] + f[k-3]
                即在F[k-1]的前面继续查找k--
                //即下次循环mid = low + f[k-1-1]-1
                 */
                k--;
            //如果key > temp[mid]，说明key在temp[mid]右边，应该继续向数组右边查找
            } else if (key > temp[mid]) {
                low = mid + 1;
                /*
                为什么是k -=2
                说明:
                1.全部元素=前面的元素＋后边元素l
                2.F[k] = f[k-1] + f[k-2]
                3.因为后面我们有f[k-2]―所以可以继续拆分f[k-1] = f[k-3] + f[k-4]
                4.即在f[k-2]的前面进行查找k -=2
                5，即下次循环mid =low + f[k - 1 - 2] - 1
                 */
                k-=2;
            }else {
                if (high >= mid){
                    return mid;
                }else {
                    //此时mid已经越界
                    return high;
                }
            }
        }
        return -1;
    }
}
